package com.janetfilter.core.commons;

import com.janetfilter.core.models.FilterRule;
import com.janetfilter.core.utils.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public class ConfigParser {
    private static final org.slf4j.Logger slf4j = org.slf4j.LoggerFactory.getLogger(ConfigParser.class);

    public static Map<String, List<FilterRule>> parse(File file) throws Exception {
        Map<String, List<FilterRule>> map = new HashMap<>();

        if (null == file || !file.exists() || !file.isFile() || !file.canRead()) {
            return map;
        }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
            int lineNumber = 0;
            String line, lastSection = null;

            List<FilterRule> ruleList = new ArrayList<>();
            while (null != (line = reader.readLine())) {
                lineNumber++;
                line = line.trim();
                if (StringUtils.isEmpty(line)) {
                    continue;
                }

                int len = line.length();
                switch (line.charAt(0)) {
                    case '[':
                        if (']' != line.charAt(len - 1)) {
                            throw new Exception("Invalid section! Line: " + lineNumber);
                        }

                        String section = line.substring(1, len - 1);
                        if (StringUtils.isEmpty(section)) {
                            throw new Exception("Empty section name! Line: " + lineNumber);
                        }

                        map.computeIfAbsent(lastSection = section, k -> new ArrayList<>());
                        ruleList = map.get(lastSection);
                        break;
                    case '#':
                    case ';':
                        break;  // comment
                    case '/':
                        if (len > 1 && '/' == line.charAt(1)) {
                            break;  // comment
                        }
                        throw new Exception("Invalid character! Line: " + lineNumber);
                    case '\\': // 换行
                        FilterRule filterRule = ruleList.get(ruleList.size() - 1);
                        if (filterRule.getRule().endsWith("\\")) { // 换行
                            filterRule = filterRule.append(line);
                        }
                        ruleList.set(ruleList.size() - 1, filterRule);
                        slf4j.info("change section: {}, rule: {}", lastSection, filterRule);
                        break;
                    default:
                        if (null == lastSection) {
                            break;  // ignore rules without section
                        }
                        int index = line.indexOf(",");
                        if (index < 1 || index > line.length()) {
                            throw new Exception("Invalid rule! Line: " + lineNumber);
                        }

                        String type = line.substring(0, index).trim();
                        String content = line.substring(index + 1).trim();

                        boolean inverse = false;
                        boolean white = false;
                        while (type.charAt(0) == '~' || type.charAt(0) == '-') {
                            if (type.charAt(0) == '~') { // 取反
                                inverse = true;
                                type = type.substring(1);
                            }
                            if (type.charAt(0) == '-') { // 白名单
                                white = true;
                                type = type.substring(1);
                            }
                        }
                        final int strategyMark = type.indexOf('&');
                        String strategy = null;
                        if (strategyMark > 0) {
                            strategy = type.substring(strategyMark + 1);
                            type = type.substring(0, strategyMark);
                        }
                        if (StringUtils.isBlank(strategy)) { // 策略名为空
                            strategy = UUID.randomUUID().toString();
                        }

                        if (StringUtils.isEmpty(type) || StringUtils.isEmpty(content)) {
                            throw new Exception("Invalid rule! Line: " + lineNumber);
                        }

                        if (!Character.isAlphabetic(type.charAt(0))) {
                            throw new Exception("Invalid rule! Line: " + lineNumber);
                        }

                        FilterRule rule = FilterRule.of(type, content, inverse, white, strategy);
                        if (null == rule) {
                            throw new Exception("Invalid rule type! Line: " + lineNumber);
                        }

                        ruleList.add(rule);
                        slf4j.info("Add section: {}, rule: {}", lastSection, rule);
                        break;
                }
            }
        }

        slf4j.info("Config file loaded: {}", file);
        return map;
    }
}
